#pragma once

/// @file userver/proto-structs/type_mapping.hpp
/// @brief Concepts and traits for checking proto struct and protobuf message compatability.

#include <cstdint>
#include <iosfwd>
#include <type_traits>

#include <userver/proto-structs/impl/traits_light.hpp>
#include <userver/utils/meta_light.hpp>
#include <userver/utils/strong_typedef_fwd.hpp>

namespace google::protobuf {

class Message;

template <typename T>
class RepeatedField;

template <typename T>
class RepeatedPtrField;

}  // namespace google::protobuf

USERVER_NAMESPACE_BEGIN

/// @brief Namespace contains types for obtaining meta information (traits, concepts, etc.).
namespace proto_structs::traits {

/// @brief Concept representing protobuf scalar type.
template <typename T>
concept ProtoScalar =
    std::is_same_v<std::remove_cv_t<T>, bool> || std::is_same_v<std::remove_cv_t<T>, std::int32_t> ||
    std::is_same_v<std::remove_cv_t<T>, std::int64_t> || std::is_same_v<std::remove_cv_t<T>, std::uint32_t> ||
    std::is_same_v<std::remove_cv_t<T>, std::uint64_t> || std::is_same_v<std::remove_cv_t<T>, std::size_t> ||
    std::is_same_v<std::remove_cv_t<T>, float> || std::is_same_v<std::remove_cv_t<T>, double> ||
    std::is_same_v<std::remove_cv_t<T>, std::string> || std::is_enum_v<T>;

/// @brief Protobuf type representing `repeated` fields in the `protoc`-generated code.
template <typename T>
concept ProtoRepeated =
    meta::kIsCvInstantiationOf<::google::protobuf::RepeatedField, T> ||
    meta::kIsCvInstantiationOf<::google::protobuf::RepeatedPtrField, T>;

/// @brief Protobuf message type generated by `protoc`.
template <typename T>
concept ProtoMessage =
    std::is_base_of_v<::google::protobuf::Message, T> &&
    !std::is_same_v<::google::protobuf::Message, std::remove_cv_t<T>>;

/// @brief Trait that marks struct/class type as compatible to some protobuf message.
///
/// By default, trait uses `T::ProtobufMessage` as its nested `Type` definition. User can also specialize
/// `CompatibleMessageTrait` for any type in the `proto_structs::traits` namespace if he can not modify the
/// code which contains `T` definition:
/// ```cpp
/// namespace proto_structs::traits {
///   template<>
///   struct CompatibleMessageTrait<ExternalStruct> {
///     using Type = SomeProtoMessage;
///   }
/// }
/// ```
template <typename T>
struct CompatibleMessageTrait {
    using Type = void;
};

template <typename T>
requires requires { typename T::ProtobufMessage; }
struct CompatibleMessageTrait<T> {
    using Type = typename T::ProtobufMessage;
};

template <typename T>
using CompatibleMessageType = typename CompatibleMessageTrait<std::remove_cv_t<T>>::Type;

/// @brief Proto struct intended for conversion to/from protobuf message type.
template <typename T>
concept ProtoStruct = std::is_class_v<T> && !std::is_same_v<CompatibleMessageType<T>, void>;

/// @brief Specifies that proto struct `T` is compatible with message `U`.
/// @tparam T proto struct type
/// @tparam U protobuf message type
template <typename T, typename U>
concept CompatibleWith = ProtoStruct<T> && std::is_same_v<CompatibleMessageType<T>, std::remove_cv_t<U>>;

}  // namespace proto_structs::traits

namespace proto_structs {

template <typename... TFields>
class Oneof;

namespace traits {

/// @brief Concept of the @ref proto_structs::Oneof.
template <typename T>
concept Oneof = impl::traits::kInheritsFromInstantiation<proto_structs::Oneof, T>;

}  // namespace traits

namespace impl {

template <std::size_t N, typename T, typename... TTail>
struct GetOneofNthType {
    using Type = typename GetOneofNthType<N - 1, TTail...>::Type;
};

template <typename T, typename... TTail>
struct GetOneofNthType<0, T, TTail...> {
    using Type = T;
};

template <std::size_t Index, typename T>
struct OneofAlternativeTrait {
    static_assert(sizeof(T) && false, "Trait should be applied to 'Oneof'");
};

template <std::size_t Index, typename... TFields>
struct OneofAlternativeTrait<Index, Oneof<TFields...>> {
    static_assert(Index < sizeof...(TFields));
    using Type = GetOneofNthType<Index, TFields...>::Type;
};

}  // namespace impl

/// @brief Special index value used to indicate `oneof` without any field set.
inline constexpr std::size_t kOneofNpos = -1;

/// @brief Provides information about @ref proto_structs::Oneof field type at position `Index`.
template <std::size_t Index, traits::Oneof TOneof>
using OneofAlternativeTrait = impl::OneofAlternativeTrait<Index, typename TOneof::Base>;

template <std::size_t Index, typename TOneof>
using OneofAlternativeType = OneofAlternativeTrait<Index, TOneof>::Type;

}  // namespace proto_structs

USERVER_NAMESPACE_END
